/*-----------------------------------------------------------------------------
 * @file   tpcan.h
 * @brief  CAN BUS protocol for TurboPilot (DroneCAN)
 * @author WLH<wanglehui8357@163.com> Copyright (C) 2021~2099.
-----------------------------------------------------------------------------*/
#ifndef TP_CAN_H
#define TP_CAN_H

#include "list.h"
#include "hal_can.h"

BEGIN_DECLS
/*-----------------------------------------------------------------------------
 * Configure define
-----------------------------------------------------------------------------*/
#define TPCAN_MAX_TX_QUEUE_CAPACITY 16u
#define TPCAN_MAX_RX_QUEUE_CAPACITY 16u
#define TPCAN_BUF_BLOCK_DATA_SIZE   127u
#define TPCAN_BUF_BLOCK_MAX_CNT     32u

/*-----------------------------------------------------------------------------
 * macro define
-----------------------------------------------------------------------------*/
#define TPCAN_MIN_NODE_ID 1u
#define TPCAN_MAX_NODE_ID 127u

/*-----------------------------------------------------------------------------
 * Data struct define
-----------------------------------------------------------------------------*/
/**
 * Error code definitions
 */
typedef enum
{
    TPCAN_ERR_NONE = 0,
    TPCAN_ERR_INVALID_PARAM = 2,
    TPCAN_ERR_OUT_OF_MEMORY = 3,
    TPCAN_ERR_NODE_ID_NOT_SET = 4,
    TPCAN_ERR_INTERNAL = 9,
} tpcan_state;

/**
 * Transfer priority definitions
 */
typedef enum
{
    TPCAN_TRANS_PRIO_HIGHEST = 0,
    TPCAN_TRANS_PRIO_HIGH = 8,
    TPCAN_TRANS_PRIO_MEDIUM = 16,
    TPCAN_TRANS_PRIO_LOW = 24,
    TPCAN_TRANS_PRIO_LOWEST = 31
} tpcan_trans_priority;

/**
 * This data type holds a CAN data frame with 29-bit ID.
 */
typedef struct
{
    U32 id;
    U8 data[CAN_FD_MAX_DATA_LEN];
    U8 len;
} tpcan_frame_t;

typedef struct
{
    list_item_t lt; /* list link node item */
    tpcan_frame_t frame;
    bool is_used;
} tpcan_txqueue_item_t;

typedef struct
{
    U8 data[TPCAN_BUF_BLOCK_DATA_SIZE]; /* Data buffer in current block */
    U8 used;                            /* Used flag of current block   */
} tpcan_buf_blk;

typedef struct
{
    list_item_t lt;     /* list link node item */
    U64 ts_us;          /* Timestamp at which the first CAN frame received. */
    U32 descriptor;     /* Transfer descriptor */
    U16 payload_crc;    /* CRC of payload      */
    tpcan_buf_blk* blk; /* use signal buffer block */
    // We're using plain 'unsigned' here, because C99 doesn't permit explicit field type specification
    unsigned crc_val     : 16;
    unsigned payload_len : 9;
    unsigned used_flag   : 1;
    unsigned trans_id    : 5;
    unsigned next_toggle : 1; // 16+9+1+5+1 = 32, aligned.
} tpcan_rxqueue_item_t;

/**
 * Transfer types are defined by the TPCAN specification.
 */
typedef enum
{
    TRANS_TYPE_RSP = 0,
    TRANS_TYPE_REQ = 1,
    TRANS_TYPE_BCST = 2
} tpcan_trans_type;

/*
 * Forward declarations.
 */
typedef struct _tpcan_inst tpcan_inst_t;
typedef struct _tpcan_rx_trans tpcan_rx_trans_t;

/**
 * The application must implement this function and
 * supply a pointer to it to the library during initialization.
 * The library calls this function to determine
 * whether the transfer should be received.
 *
 * Signature for any data type can be obtained in many ways;
 */
typedef bool (*tpcan_should_accept)(
    tpcan_inst_t* ins,           /* Library instance               */
    U64* signature,              /* Data type signature            */
    const U16 dt_id,             /* Refer to the specification     */
    tpcan_trans_type trans_type, /* Refer to tpcan_trans_type      */
    const U8 src_node_id);       /* Source node ID or Broadcast(0) */

/**
 * This function will be invoked by the library every time a transfer is successfully received.
 */
typedef void (*tpcan_on_reception)(tpcan_inst_t* ins, tpcan_rx_trans_t* transfer);

/**
 * This is the core structure that keeps all of the states
 * and allocated resources of the library instance.
 * The application should never access any of the fields directly!
 * Instead, API functions should be used.
 */
struct _tpcan_inst
{
    U8 node_id;                        /* Local node ID; set zero if the node is anonymous */
    bool fd_mode;                      /* Flag for use CAN FD mode                         */
    list_t rx_queue;                   /* RX transfer states queue                         */
    list_t tx_queue;                   /* TX frames awaiting transmission                  */
    tpcan_should_accept should_accept; /* Function to decide whether wants the transfer    */
    tpcan_on_reception on_reception;   /* Function be called after RX transfer is cplt     */

    /* private member */
    tpcan_buf_blk blk_items[TPCAN_BUF_BLOCK_MAX_CNT];
    tpcan_rxqueue_item_t rx_items[TPCAN_MAX_RX_QUEUE_CAPACITY];
    tpcan_txqueue_item_t tx_items[TPCAN_MAX_TX_QUEUE_CAPACITY];
};

/**
 * This structure represents a received transfer for the application.
 * An instance of it is passed to the application via callback
 * when the library receives a new transfer.
 * Pointers to the structure and all its fields
 * are invalidated after the callback returns.
 */
struct _tpcan_rx_trans
{
    U64 ts_us;                     /* Timestamp at which the last frame of this transfer was received. */
    tpcan_rxqueue_item_t* rx_item; /* Pointer to a rxqueue item  */

    /* These fields identify the transfer for the application. */
    U16 dt_id;      /* 0 to 255 for services, 0 to 65535 for messages */
    U8 trans_type;  /* See tpcan_trans_type */
    U8 trans_id;    /* 0 to 31              */
    U8 priority;    /* 0 to 31              */
    U8 src_node_id; /* 1 to 127, or 0 if the source is anonymous */
};

/*
 * Pointer to the transfer ID should point to a persistent variable (e.g. static or heap allocated, not on the stack);
 * it will be updated by the library after every transmission. The transfer ID value cannot be shared between
 * transfers that have different descriptors! More on this in the transport layer specification.
 *
 * For Request transfers,
 * The transfer ID will be updated by the library after every transmission.
 * The transfer ID cannot be shared between transfers that have different descriptors!
 * More on this in the transport layer specification.
 *
 * For Response transfers, the Transfer ID will be treated as const (i.e. read-only),
 * and normally it should point to the trans_id field of the structure tpcan_rx_trans_t.
*/
typedef struct
{
    U16 dt_id;         /* 0~255 for services, 0~65535 for messages  */
    U16 len;           /* Length of the above, in bytes             */
    U8 priority;       /* Refer to definitions tpcan_trans_priority */
    U64 signature;     /* Data signature for multi frame transfer   */
    const U8* payload; /* Transfer payload data pointer             */
    U8* trans_id;      /* Pointer to a persistent variable containing the transfer ID */
} tpcan_msg_t;

/*-----------------------------------------------------------------------------
 * public functions
-----------------------------------------------------------------------------*/
/**
 * @brief Initializes a library instance.
 * Local node ID will be set to zero, i.e. the node will be anonymous.
 *
 * Typically, size of the memory pool should not be less than 1K.
 * The recommended way to detect the required pool size is to
 * measure the peak pool usage after a stress-test.
 */
void tpcan_init(tpcan_inst_t* ins, const U8 node_id, const bool fd_mode);
/**
 * @brief Sends a broadcast transfer message.
 * !!! NOT support anonymous transfers !!!
 * Please refer to the specification for more details about data type signatures.
 */
tpcan_state tpcan_broadcast(tpcan_inst_t* ins, tpcan_msg_t* msg);
/**
 * @brief Sends a request transfer.
 */
tpcan_state tpcan_send_req(tpcan_inst_t* ins, const U8 dst_node_id, const tpcan_msg_t* msg);
/**
 * @brief Sends a response transfer.
 */
tpcan_state tpcan_send_rsp(tpcan_inst_t* ins, const U8 dst_node_id, const tpcan_msg_t* msg);
/**
 * @brief Send all TX frame by hal can API
 */
void tpcan_flush_tx_queue(tpcan_inst_t* ins, const U8 port);
/**
 * @brief Processes a received CAN frame (with timestamp).
 * The application will call this function when it receives a new frame from the CAN bus.
 */
void tpcan_handle_can_frame(tpcan_inst_t* ins, can_frame_t* frame);
/**
 * @brief Traverses the list of transfers and removes those
 * that were last updated more than timeout microseconds ago.
 * This function must be invoked by the application periodically, about once a second.
 */
void tpcan_cleanup_stale_transfers(tpcan_inst_t* ins, const U64 ts_us);

/*-----------------------------------------------------------------------------
 * Utils functions
-----------------------------------------------------------------------------*/


END_DECLS

#endif //~TP_CAN_H
